<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="keywords" content="Hexo Theme Redefine">
    
    <meta name="author" content="xiaoeryu">
    <!-- preconnect -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

    
    <!--- Seo Part-->
    
    <link rel="canonical" href="https://xiaoeeyu.github.io/2024/07/29/自编译openssl库的抓包与溯源/"/>
    <meta name="robots" content="index,follow">
    <meta name="googlebot" content="index,follow">
    <meta name="revisit-after" content="1 days">
    
    
    
        
        <meta name="description" content="前面几章我们分析了在Java层和JNI层中使用HTTP&#x2F;HTTPS发送数据和接收时，怎么去进行抓包与溯源 有些App会不使用Android系统提供的SSL库，而使用开源的openssl自己编译一个本地库，用自己的这个库来完成SSL通信。这种情况下我们再对SSL_read&#x2F;SSL_write进行hook就无法拿到发送和接收的数据了。 甚至APP在有些场景下会直接写汇编使用系统调用号直接与内核交互来进">
<meta property="og:type" content="article">
<meta property="og:title" content="自编译openssl库的抓包与溯源">
<meta property="og:url" content="https://xiaoeeyu.github.io/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/index.html">
<meta property="og:site_name" content="xiaoeryu">
<meta property="og:description" content="前面几章我们分析了在Java层和JNI层中使用HTTP&#x2F;HTTPS发送数据和接收时，怎么去进行抓包与溯源 有些App会不使用Android系统提供的SSL库，而使用开源的openssl自己编译一个本地库，用自己的这个库来完成SSL通信。这种情况下我们再对SSL_read&#x2F;SSL_write进行hook就无法拿到发送和接收的数据了。 甚至APP在有些场景下会直接写汇编使用系统调用号直接与内核交互来进">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://xiaoeeyu.github.io/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240705110143920.png">
<meta property="og:image" content="https://xiaoeeyu.github.io/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240705110651313.png">
<meta property="og:image" content="https://xiaoeeyu.github.io/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240705173525169.png">
<meta property="og:image" content="https://xiaoeeyu.github.io/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240706181440550.png">
<meta property="og:image" content="https://xiaoeeyu.github.io/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240706220712317.png">
<meta property="og:image" content="https://xiaoeeyu.github.io/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240706232355556.png">
<meta property="article:published_time" content="2024-07-29T00:19:13.000Z">
<meta property="article:modified_time" content="2024-07-29T01:45:38.791Z">
<meta property="article:author" content="xiaoeryu">
<meta property="article:tag" content="App抓包">
<meta property="article:tag" content="Frida Hook">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://xiaoeeyu.github.io/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240705110143920.png">
    
    
    <!--- Icon Part-->
    <link rel="icon" type="image/png" href="/images/rabete.jpg" sizes="192x192">
    <link rel="apple-touch-icon" sizes="180x180" href="/images/rabete.jpg">
    <meta name="theme-color" content="#A31F34">
    <link rel="shortcut icon" href="/images/rabete.jpg">
    <!--- Page Info-->
    
    <title>
        
            自编译openssl库的抓包与溯源 | xiaoeryu
        
    </title>

    
<link rel="stylesheet" href="/fonts/Chillax/chillax.css">


    <!--- Inject Part-->
    

    
<link rel="stylesheet" href="/css/style.css">


    
        
<link rel="stylesheet" href="/css/build/tailwind.css">

    

    
<link rel="stylesheet" href="/fonts/GeistMono/geist-mono.css">

    
<link rel="stylesheet" href="/fonts/Geist/geist.css">

    <!--- Font Part-->
    
    
    
    
    
    

    <script id="hexo-configurations">
    window.config = {"hostname":"xiaoeeyu.github.io","root":"/","language":"zh-CN","path":"search.xml"};
    window.theme = {"articles":{"style":{"font_size":"16px","line_height":1.5,"image_border_radius":"14px","image_alignment":"center","image_caption":false,"link_icon":true,"delete_mask":false,"title_alignment":"left","headings_top_spacing":{"h1":"3.2rem","h2":"2.4rem","h3":"1.9rem","h4":"1.6rem","h5":"1.4rem","h6":"1.3rem"}},"word_count":{"enable":true,"count":true,"min2read":true},"author_label":{"enable":true,"auto":false,"list":[]},"code_block":{"copy":true,"style":"mac","highlight_theme":{"light":"github","dark":"vs2015"},"font":{"enable":false,"family":null,"url":null}},"toc":{"enable":true,"max_depth":4,"number":false,"expand":true,"init_open":true},"copyright":{"enable":true,"default":"cc_by_nc_sa"},"lazyload":true,"pangu_js":false,"recommendation":{"enable":false,"title":"推荐阅读","limit":3,"mobile_limit":2,"placeholder":"/images/ball-0101.jpg","skip_dirs":[]}},"colors":{"primary":"#A31F34","secondary":null,"default_mode":"light"},"global":{"fonts":{"chinese":{"enable":false,"family":null,"url":null},"english":{"enable":false,"family":null,"url":null},"title":{"enable":false,"family":null,"url":null}},"content_max_width":"1000px","sidebar_width":"210px","hover":{"shadow":true,"scale":false},"scroll_progress":{"bar":false,"percentage":true},"website_counter":{"url":"https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js","enable":true,"site_pv":true,"site_uv":true,"post_pv":true},"single_page":true,"preloader":{"enable":false,"custom_message":null},"open_graph":true,"google_analytics":{"enable":false,"id":null}},"home_banner":{"enable":true,"style":"fixed","image":{"light":"/images/wallhaven-jxl31y.png","dark":"/images/wallhaven-o5762l.png"},"title":"XIAOERYU","subtitle":{"text":["明心见性，拨云见日","Don't wait, to create"],"hitokoto":{"enable":false,"show_author":false,"api":"https://v1.hitokoto.cn"},"typing_speed":100,"backing_speed":80,"starting_delay":500,"backing_delay":1500,"loop":true,"smart_backspace":true},"text_color":{"light":"#fff","dark":"#d1d1b6"},"text_style":{"title_size":"2.8rem","subtitle_size":"1.5rem","line_height":1.2},"custom_font":{"enable":false,"family":null,"url":null},"social_links":{"enable":true,"style":"default","links":{"github":"https://github.com/xiaoeeyu","instagram":null,"zhihu":null,"twitter":null,"email":"xiaoeryu@163.com"},"qrs":{"weixin":null}}},"plugins":{"feed":{"enable":false},"aplayer":{"enable":false,"type":"fixed","audios":[{"name":null,"artist":null,"url":null,"cover":null,"lrc":null}]},"mermaid":{"enable":false,"version":"9.3.0"}},"version":"2.8.2","navbar":{"auto_hide":false,"color":{"left":"#f78736","right":"#367df7","transparency":35},"width":{"home":"1200px","pages":"1000px"},"links":{"Home":{"path":"/","icon":"fa-regular fa-house"},"Archives":{"path":"/archives","icon":"fa-regular fa-archive"}},"search":{"enable":true,"preload":true}},"page_templates":{"friends_column":2,"tags_style":"blur"},"home":{"sidebar":{"enable":true,"position":"left","first_item":"menu","announcement":null,"show_on_mobile":true,"links":null},"article_date_format":"auto","excerpt_length":200,"categories":{"enable":true,"limit":3},"tags":{"enable":true,"limit":3}},"footerStart":"2022/8/17 11:45:14"};
    window.lang_ago = {"second":"%s 秒前","minute":"%s 分钟前","hour":"%s 小时前","day":"%s 天前","week":"%s 周前","month":"%s 个月前","year":"%s 年前"};
    window.data = {"masonry":false};
  </script>
    
    <!--- Fontawesome Part-->
    
<link rel="stylesheet" href="/fontawesome/fontawesome.min.css">

    
<link rel="stylesheet" href="/fontawesome/brands.min.css">

    
<link rel="stylesheet" href="/fontawesome/solid.min.css">

    
<link rel="stylesheet" href="/fontawesome/regular.min.css">

    
    
    
    
<meta name="generator" content="Hexo 6.3.0">
<style>.github-emoji { position: relative; display: inline-block; width: 1.2em; min-height: 1.2em; overflow: hidden; vertical-align: top; color: transparent; }  .github-emoji > span { position: relative; z-index: 10; }  .github-emoji img, .github-emoji .fancybox { margin: 0 !important; padding: 0 !important; border: none !important; outline: none !important; text-decoration: none !important; user-select: none !important; cursor: auto !important; }  .github-emoji img { height: 1.2em !important; width: 1.2em !important; position: absolute !important; left: 50% !important; top: 50% !important; transform: translate(-50%, -50%) !important; user-select: none !important; cursor: auto !important; } .github-emoji-fallback { color: inherit; } .github-emoji-fallback img { opacity: 0 !important; }</style>
</head>



<body>
	<div class="progress-bar-container">
	

	
	<span class="pjax-progress-bar"></span>
	<!--        <span class="swup-progress-icon">-->
	<!--            <i class="fa-solid fa-circle-notch fa-spin"></i>-->
	<!--        </span>-->
	
</div>

<main class="page-container" id="swup">

	

	<div class="main-content-container flex flex-col justify-between min-h-dvh">
		<div class="main-content-header">
			<header class="navbar-container px-6 md:px-12">
    <div class="navbar-content transition-navbar ">
        <div class="left">
            
                <a class="logo-image h-8 w-8 sm:w-10 sm:h-10 mr-3" href="/">
                    <img src="/images/rabete.jpg" class="w-full h-full rounded-sm">
                </a>
            
            <a class="logo-title" href="/">
                
                xiaoeryu
                
            </a>
        </div>

        <div class="right">
            <!-- PC -->
            <div class="desktop">
                <ul class="navbar-list">
                    
                        
                            

                            <li class="navbar-item">
                                <!-- Menu -->
                                <a class=""
                                   href="/"
                                        >
                                    <i class="fa-regular fa-house fa-fw"></i>
                                    首页
                                    
                                </a>

                                <!-- Submenu -->
                                
                            </li>
                    
                        
                            

                            <li class="navbar-item">
                                <!-- Menu -->
                                <a class=""
                                   href="/archives"
                                        >
                                    <i class="fa-regular fa-archive fa-fw"></i>
                                    归档
                                    
                                </a>

                                <!-- Submenu -->
                                
                            </li>
                    
                    
                        <li class="navbar-item search search-popup-trigger">
                            <i class="fa-solid fa-magnifying-glass"></i>
                        </li>
                    
                </ul>
            </div>
            <!-- Mobile -->
            <div class="mobile">
                
                    <div class="icon-item search search-popup-trigger"><i class="fa-solid fa-magnifying-glass"></i>
                    </div>
                
                <div class="icon-item navbar-bar">
                    <div class="navbar-bar-middle"></div>
                </div>
            </div>
        </div>
    </div>

    <!-- Mobile sheet -->
    <div class="navbar-drawer h-dvh w-full absolute top-0 left-0 bg-background-color flex flex-col justify-between">
        <ul class="drawer-navbar-list flex flex-col px-4 justify-center items-start">
            
                
                    

                    <li class="drawer-navbar-item text-base my-1.5 flex flex-col w-full">
                        
                        <a class="py-1.5 px-2 flex flex-row items-center justify-between gap-1 hover:!text-primary active:!text-primary text-2xl font-semibold group border-b border-border-color hover:border-primary w-full "
                           href="/"
                        >
                            <span>
                                首页
                            </span>
                            
                                <i class="fa-regular fa-house fa-sm fa-fw"></i>
                            
                        </a>
                        

                        
                    </li>
            
                
                    

                    <li class="drawer-navbar-item text-base my-1.5 flex flex-col w-full">
                        
                        <a class="py-1.5 px-2 flex flex-row items-center justify-between gap-1 hover:!text-primary active:!text-primary text-2xl font-semibold group border-b border-border-color hover:border-primary w-full "
                           href="/archives"
                        >
                            <span>
                                归档
                            </span>
                            
                                <i class="fa-regular fa-archive fa-sm fa-fw"></i>
                            
                        </a>
                        

                        
                    </li>
            

            
            
        </ul>

        <div class="statistics flex justify-around my-2.5">
    <a class="item tag-count-item flex flex-col justify-center items-center w-20" href="/tags">
        <div class="number text-2xl sm:text-xl text-second-text-color font-semibold">92</div>
        <div class="label text-third-text-color text-sm">标签</div>
    </a>
    <a class="item tag-count-item flex flex-col justify-center items-center w-20" href="/categories">
        <div class="number text-2xl sm:text-xl text-second-text-color font-semibold">14</div>
        <div class="label text-third-text-color text-sm">分类</div>
    </a>
    <a class="item tag-count-item flex flex-col justify-center items-center w-20" href="/archives">
        <div class="number text-2xl sm:text-xl text-second-text-color font-semibold">112</div>
        <div class="label text-third-text-color text-sm">文章</div>
    </a>
</div>
    </div>

    <div class="window-mask"></div>

</header>


		</div>

		<div class="main-content-body transition-fade-up">
			

			<div class="main-content">
				<div class="post-page-container flex relative justify-between box-border w-full h-full">
	<div class="article-content-container">

		<div class="article-title relative w-full">
			
			<div class="w-full flex items-center pt-6 justify-start">
				<h1 class="article-title-regular text-second-text-color tracking-tight text-4xl md:text-6xl font-semibold px-2 sm:px-6 md:px-8 py-3">自编译openssl库的抓包与溯源</h1>
			</div>
			
		</div>

		
		<div class="article-header flex flex-row gap-2 items-center px-2 sm:px-6 md:px-8">
			<div class="avatar w-[46px] h-[46px] flex-shrink-0 rounded-medium border border-border-color p-[1px]">
				<img src="/images/rabete.jpg">
			</div>
			<div class="info flex flex-col justify-between">
				<div class="author flex items-center">
					<span class="name text-default-text-color text-lg font-semibold">xiaoeryu</span>
					
					<span class="author-label ml-1.5 text-xs px-2 py-0.5 rounded-small text-third-text-color border border-shadow-color-1">Lv5</span>
					
				</div>
				<div class="meta-info">
					<div class="article-meta-info">
    <span class="article-date article-meta-item">
        <i class="fa-regular fa-pen-fancy"></i>&nbsp;
        <span class="desktop">2024-07-29 08:19:13</span>
        <span class="mobile">2024-07-29 08:19:13</span>
        <span class="hover-info">创建</span>
    </span>
    
        <span class="article-date article-meta-item">
            <i class="fa-regular fa-wrench"></i>&nbsp;
            <span class="desktop">2024-07-29 09:45:38</span>
            <span class="mobile">2024-07-29 09:45:38</span>
            <span class="hover-info">更新</span>
        </span>
    

    
        <span class="article-categories article-meta-item">
            <i class="fa-regular fa-folders"></i>&nbsp;
            <ul>
                
                
                    
                        
                        <li>
                            <a href="/categories/Android%E9%80%86%E5%90%91/">Android逆向</a>&nbsp;
                        </li>
                    
                    
                
            </ul>
        </span>
    
    
        <span class="article-tags article-meta-item">
            <i class="fa-regular fa-tags"></i>&nbsp;
            <ul>
                
                    <li>
                        <a href="/tags/App%E6%8A%93%E5%8C%85/">App抓包</a>&nbsp;
                    </li>
                
                    <li>
                        | <a href="/tags/Frida-Hook/">Frida Hook</a>&nbsp;
                    </li>
                
            </ul>
        </span>
    

    
    
    
    
        <span class="article-pv article-meta-item">
            <i class="fa-regular fa-eye"></i>&nbsp;<span id="busuanzi_value_page_pv"></span>
        </span>
    
</div>

				</div>
			</div>
		</div>
		

		


		<div class="article-content markdown-body px-2 sm:px-6 md:px-8 pb-8">
			<p>前面几章我们分析了在Java层和JNI层中使用HTTP/HTTPS发送数据和接收时，怎么去进行抓包与溯源</p>
<p>有些App会不使用Android系统提供的SSL库，而使用开源的<strong>openssl</strong>自己编译一个本地库，用自己的这个库来完成SSL通信。这种情况下我们再对<code>SSL_read/SSL_write</code>进行hook就无法拿到发送和接收的数据了。</p>
<p>甚至APP在有些场景下会直接写汇编使用系统调用号直接与内核交互来进行数据的接收和发送，这样会绕过了使用<code>sendto/recvfrom/read/write</code>这些函数，这样的操作会更加的隐蔽。</p>
<p>接下来我们分别分析，在这些情况下如何抓包。</p>
<p><strong>PS：本章中有些代码在Android11.0环境下没有生效，所以使用了8.0的环境，frida需要使用12.8.0有些在frida16上生效的代码在12.8上不生效。需要修改一下不再赘述</strong></p>
<h2 id="使用私有SSL库通信"><a href="#使用私有SSL库通信" class="headerlink" title="使用私有SSL库通信"></a>使用私有SSL库通信</h2><p>使用C编写标准SSL通信的时候，有一个关键的一步-&gt;建立SSL通信之后需要去调用<code>SSL_get_fd</code>来完成当前的socketID和当前的SSL的绑定。所以利用这一点可以通过<code>SSL_get_fd</code>来获取当前SSL的socketID</p>
<img lazyload="" src="/images/loading.svg" data-src="/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240705110143920.png" class="" title="image-20240705110143920">

<ul>
<li>通过主动调用<code>SSL_get_fd</code>传入SSL，可以得到当前SSL通信过程当中的socketID</li>
</ul>
<p>用设备中拷贝出<strong>libssl.so</strong>用IDA查看（环境是Android11.0）</p>
<img lazyload="" src="/images/loading.svg" data-src="/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240705110651313.png" class="" title="image-20240705110651313">

<ul>
<li>可以看到<code>SSL_get_fd</code>是一个导出函数，32位和64位都有。可以使用frida直接调用</li>
</ul>
<h4 id="编写脚本"><a href="#编写脚本" class="headerlink" title="编写脚本"></a>编写脚本</h4><p>只需要对上一章的脚本稍微进行一些修改，从libssl.so中找到<code>SSL_get_fd</code>然后拿到返回值<strong>socketID</strong>，对<strong>socketID</strong>进行解析就能拿到IP和端口</p>
<pre><code class="js">function LogPrint(log) {
    // Get the current date and time
    var theDate = new Date();
    var hour = theDate.getHours();
    var minute = theDate.getMinutes();
    var second = theDate.getSeconds();
    var mSecond = theDate.getMilliseconds();

    // Format the time components to ensure two-digit display
    hour = hour &lt; 10 ? "0" + hour : hour;
    minute = minute &lt; 10 ? "0" + minute : minute;
    second = second &lt; 10 ? "0" + second : second;
    mSecond = mSecond &lt; 10 ? "00" + mSecond : mSecond &lt; 100 ? "0" + mSecond : mSecond;

    // Construct the time string
    var time = hour + ":" + minute + ":" + second + ":" + mSecond;
    var threadid = Process.getCurrentThreadId();

    // Log the message with the timestamp and thread ID
    console.log("[" + time + "]" + "-&gt;threadid:" + threadid + "--" + log);
}

function printNativeStack(context, name) {
    // Get the native backtrace from the provided context
    var array = Thread.backtrace(context, Backtracer.ACCURATE);

    // Check if the first symbol is related to 'libopenjdk.so!NET_Send'
    var first = DebugSymbol.fromAddress(array[0]);
    if (first.toString().indexOf('libopenjdk.so!NET_Send') &lt; 0) {
        // Log the backtrace if it doesn't match
        var trace = Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\n");
        LogPrint("-----------start:" + name + "--------------");
        LogPrint(trace);
        LogPrint("-----------end:" + name + "--------------");
    }
}

function printJavaStack(name) {
    Java.perform(function () {
        var Exception = Java.use("java.lang.Exception");
        var ins = Exception.$new("Exception");
        var straces = ins.getStackTrace();
        if (straces != undefined &amp;&amp; straces != null) {
            var strace = straces.toString();
            var replaceStr = strace.replace(/,/g, " \n ");
            LogPrint("=============================" + name + " Stack start=======================");
            LogPrint(replaceStr);
            LogPrint("=============================" + name + " Stack end======================= \n ");
            Exception.$dispose();
        }
    });
}

function isprintable(value) {
    return (value &gt;= 32 &amp;&amp; value &lt;= 126);
}

function getsocketdetail(fd) {
    var result = "";
    var type = Socket.type(fd);
    if (type != null) {
        result = result + "type:" + type;
        var peer = Socket.peerAddress(fd);
        var local = Socket.localAddress(fd);
        console.log("Peer address: " + JSON.stringify(peer) + ", Local address: " + JSON.stringify(local));
        result = result + ", address:" + JSON.stringify(peer) + ", local:" + JSON.stringify(local);
    } else {
        result = "unknown";
    }
    return result;
}

function hooklibssl() {
    // SSL_write(SSL *ssl, const void *buf, int num)
    // int SSL_get_fd(const SSL *ssl) { return SSL_get_rfd(ssl);
    var libsslmodule = Process.getModuleByName("libssl.so");
    var SSL_read_addr = libsslmodule.getExportByName("SSL_read");
    var SSL_write_addr = libsslmodule.getExportByName("SSL_write");
    var SSL_get_fd_addr = libsslmodule.getExportByName("SSL_get_rfd");
    var SSL_get_fd = new NativeFunction(SSL_get_fd_addr, 'int', ['pointer']);


    Interceptor.attach(SSL_read_addr, {
        onEnter: function (args) {
            this.ssl = args[0];
            this.buf = args[1];
            this.num = args[2];

            LogPrint("go into libssl.so-&gt;SSL_read");
            printNativeStack(this.context, Process.getCurrentThreadId() + "SSL_read");
        },
        onLeave: function (retval) {
            var size = retval.toInt32();
            if (size &gt; 0) {
                console.log("SSL object: " + this.ssl);
                var sockfd = SSL_get_fd(this.ssl);
                console.log("SSL_get_fd returned: " + sockfd);
                var socketdetail = getsocketdetail(sockfd);

                console.log(Process.getCurrentThreadId() + socketdetail + "---libssl.so-&gt;SSL_read:" + hexdump(this.buf, {
                    length: size
                }));
            }

            LogPrint("leave libssl.so-&gt;SSL_read");
        },
    });

    Interceptor.attach(SSL_write_addr, {
        onEnter: function (args) {
            this.arg0 = args[0];
            this.arg1 = args[1];
            this.arg2 = args[2];

            LogPrint("go into libssl.so-&gt;SSL_write");
            printNativeStack(this.context, Process.getCurrentThreadId() + "SSL_write");
        },
        onLeave: function (retval) {
            var size = ptr(this.arg2).toInt32();
            if (size &gt; 0) {
                var sockfd = SSL_get_fd(this.arg0);
                var socketdetail = getsocketdetail(sockfd);

                console.log(Process.getCurrentThreadId() + socketdetail + "---libssl.so-&gt;SSL_write:" + hexdump(this.arg1, {
                    length: size
                }));
            }

            LogPrint("leave libssl.so-&gt;SSL_write");
        },
    });
}

function main() {
    hooklibssl();
}

setImmediate(main);
</code></pre>
<ul>
<li>有点奇怪，脚本在Android11.0的设备上执行，返回值出错了，换了8.0的设备执行ok</li>
<li>不过Android8.0的64位没有<code>SSL_get_fd</code>这个函数换成<code>SSL_get_rfd</code>也可以</li>
<li>暂时先换8.0的设备，回头再找问题在哪</li>
</ul>
<h5 id="执行结果"><a href="#执行结果" class="headerlink" title="执行结果"></a>执行结果</h5><p>用了滴答清单App的登陆功能进行测试</p>
<h6 id="Android8-0"><a href="#Android8-0" class="headerlink" title="Android8.0"></a>Android8.0</h6><img lazyload="" src="/images/loading.svg" data-src="/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240705173525169.png" class="" title="image-20240705173525169">

<h6 id="Android11-0"><a href="#Android11-0" class="headerlink" title="Android11.0"></a>Android11.0</h6><pre><code>SSL object: 0x7b9ea45898
SSL_get_fd returned: -1
getsocketdetail called with fd: -1
Socket type: null
27813unknown--------libssl.so-&gt;SSL_read: 
</code></pre>
<ul>
<li>返回值报错，返回了<code>-1</code></li>
</ul>
<h4 id="扩大搜索范围"><a href="#扩大搜索范围" class="headerlink" title="扩大搜索范围"></a>扩大搜索范围</h4><p>扩大搜索范围在所有加载的模块中匹配，即使没有<strong>libssl.so</strong>只要符号没有抹除掉就能找到目标函数</p>
<pre><code class="js">function hookallssl() {
    var libsslmodule = Process.getModuleByName("libssl.so");
    var SSL_get_rfd_ptr = libsslmodule.getExportByName('SSL_get_rfd');
    var SSL_get_rfd = new NativeFunction(SSL_get_rfd_ptr, 'int', ['pointer']);
    Process.enumerateModules().forEach(function (module) {
        module.enumerateExports().forEach(function (symbol) {
            var name = symbol.name;
            if (name == 'SSL_read') {
                LogPrint(JSON.stringify(module) + JSON.stringify(symbol));
            }
            if (name == 'SSL_write') {
                LogPrint(JSON.stringify(module) + JSON.stringify(symbol));
                Interceptor.attach(symbol.address, {
                    onEnter: function (args) {
                        this.arg0 = args[0];
                        this.arg1 = args[1];
                        this.arg2 = args[2];
                        LogPrint("go into " + Process.getCurrentThreadId() + "---" + JSON.stringify(module) + "---" + JSON.stringify(symbol));
                        printNativeStack(this.context, Process.getCurrentThreadId() + "---" + JSON.stringify(module) + "---" + JSON.stringify(symbol));
                        var size = ptr(this.arg2).toInt32();
                        if (size &gt; 0) {
                            var sockfd = SSL_get_rfd(this.arg0);
                            var socketdetail = getsocketdetail(sockfd);
                            console.log(socketdetail + "---" + Process.getCurrentThreadId() + "---" + JSON.stringify(module) + "---" + JSON.stringify(symbol) + hexdump(this.arg1, {
                                length: size
                            }));
                        }
                    }, onLeave(retval) {
                        LogPrint("leave " + Process.getCurrentThreadId() + "---" + JSON.stringify(module) + "---" + JSON.stringify(symbol));
                    }
                });
            }
        })
    })
}
</code></pre>
<p>总结：本节主要目的在于如果App在进行HTTP/SSL进行加密通信的时候，如果使用的是自己编译的openssl库，可以选择hook第三方库中的<code>SSL_write/read</code>方法，另外通过调用 <code>SSL_get_rfd</code> 的主要目的是获取 SSL 连接的文件描述符，以便进一步获取网络连接的详细信息。这些信息可以帮助分析和调试网络通信，特别是当涉及加密通信时，通过文件描述符可以关联具体的网络连接，从而更好地理解和分析数据的传输过程。</p>
<h3 id="无符号库"><a href="#无符号库" class="headerlink" title="无符号库"></a>无符号库</h3><p>前面分析了App没有使用系统SSL库，使用了自己编译的openssl库通信，应该如何抓包。如果符号还在的话较为简单，还是一样对他使用的SSL库进行遍历就拿到<code>SSL_write</code>等hook点就可以了。</p>
<p>因为<strong>openssl/boringssl</strong>是开源的，大家都可以修改。虽然一般情况下开发人员不会进行大量的修改，经常是把符号抹除掉。</p>
<h4 id="常用的三种方法"><a href="#常用的三种方法" class="headerlink" title="常用的三种方法"></a>常用的三种方法</h4><p>那如果符号被抹去了我们应该怎么抓包呢呢？</p>
<blockquote>
<p>在符号被抹除的情况下，一般来说还可以通过以下三种方法来找到拦截目标函数</p>
<p><strong>使用基于地址的拦截</strong>：如果您知道目标函数的相对地址或偏移量，可以直接拦截该地址。</p>
<blockquote>
<p>假设我们知道了<code>ssl_write_offset = 0x123456</code>，那么只需要获取到当前模块的基址然后相加就可以了，不过这种方式随着编译方式的不同，或者库代码的改变等等都会改变偏移的位置。不确定性太大</p>
<pre><code class="js">var baseAddress = Module.findBaseAddress('libssl.so');  // 获取 libssl.so 的基址
var ssl_write_offset = 0x123456;  // 假设 SSL_write 的偏移量是 0x123456
var ssl_write_address = baseAddress.add(ssl_write_offset);  // 计算 SSL_write 的绝对地址
</code></pre>
</blockquote>
<p><strong>使用模式匹配</strong>：通过字节码模式匹配找到目标函数的位置。</p>
<blockquote>
<p>在模块中暴力匹配特定的字节码来定位目标函数，比通过偏移来寻找会更为可靠一些。下面以它为例编写代码</p>
</blockquote>
<p><strong>通过函数调用上下文或特征识别</strong>：分析目标函数的调用上下文或其他特征来识别并拦截。</p>
<blockquote>
<p>调用上下文或特征识别的方法主要通过分析目标函数在运行时的行为或周边环境来定位和拦截它。即使符号被抹除，这些方法仍然可以有效地识别目标函数。这些方法包括但不限于：</p>
<ol>
<li><strong>调用堆栈特征识别</strong>：分析函数调用的堆栈特征。</li>
<li><strong>参数特征识别</strong>：通过函数调用时的参数特征来识别。</li>
<li><strong>上下文特征识别</strong>：通过函数调用前后的上下文信息来识别。</li>
</ol>
</blockquote>
</blockquote>
<h4 id="字节匹配模式脚本："><a href="#字节匹配模式脚本：" class="headerlink" title="字节匹配模式脚本："></a>字节匹配模式脚本：</h4><pre><code class="js">function LogPrint(log) {
    var theDate = new Date();
    var hour = theDate.getHours();
    var minute = theDate.getMinutes();
    var second = theDate.getSeconds();
    var mSecond = theDate.getMilliseconds();

    hour &lt; 10 ? hour = "0" + hour : hour;
    minute &lt; 10 ? minute = "0" + minute : minute;
    second &lt; 10 ? second = "0" + second : second;
    mSecond &lt; 10 ? mSecond = "00" + mSecond : mSecond &lt; 100 ? mSecond = "0" + mSecond : mSecond;
    var time = hour + ":" + minute + ":" + second + ":" + mSecond;
    var threadid = Process.getCurrentThreadId();
    console.log("[" + time + "]" + "-&gt;threadid:" + threadid + "--" + log);
}

function printNativeStack(context, name) {
    var trace = Thread.backtrace(context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join("\n");
    LogPrint("-----------start:" + name + "--------------");
    LogPrint(trace);
    LogPrint("-----------end:" + name + "--------------");
}

function printJavaStack(name) {
    Java.perform(function () {
        var Exception = Java.use("java.lang.Exception");
        var ins = Exception.$new("Exception");
        var straces = ins.getStackTrace();
        if (straces != undefined &amp;&amp; straces != null) {
            var strace = straces.toString();
            var replaceStr = strace.replace(/,/g, " \n ");
            LogPrint("=============================" + name + " Stack strat=======================");
            LogPrint(replaceStr);
            LogPrint("=============================" + name + " Stack end======================= \n ");
            Exception.$dispose();
        }
    });
}

function isprintable(value) {
    return value &gt;= 32 &amp;&amp; value &lt;= 126;
}

function getsocketdetail(fd) {
    var result = "";
    var type = Socket.type(fd);
    if (type != null) {
        result = result + "type:" + type;
        var peer = Socket.peerAddress(fd);
        var local = Socket.localAddress(fd);
        result = result + ",address:" + JSON.stringify(peer) + ",local:" + JSON.stringify(local);
    } else {
        result = "unknown";
    }
    return result;
}

function getip(ip_ptr) {
    var result = ptr(ip_ptr).readU8() + "." + ptr(ip_ptr.add(1)).readU8() + "." + ptr(ip_ptr.add(2)).readU8() + "." + ptr(ip_ptr.add(3)).readU8();
    return result;
}

function SSL_get_rfd(ssl) {
    var SSL_get_rfd_ptr = Module.findExportByName("libssl.so", "SSL_get_rfd");
    var SSL_get_rfd_func = new NativeFunction(SSL_get_rfd_ptr, 'int', ['pointer']);
    return SSL_get_rfd_func(ssl);
}

function hookMatchbytecode() {
    var baseAddress = Module.findBaseAddress('libssl.so');  // 获取 libssl.so 的基址
    if (baseAddress === null) {
        console.error('Could not find base address for libssl.so');
        return;
    }

    // 获取 libssl.so 的所有可执行范围
    var ranges = Process.enumerateRangesSync({
        protection: 'r-x',
        coalesce: true
    }).filter(function (range) {
        return range.file &amp;&amp; range.file.path.indexOf('libssl.so') !== -1;
    });

    // var sizeOfModule = ranges.reduce((total, range) =&gt; total + range.size, 0);
    var sizeOfModule = 0;
    ranges.forEach(function (range) {
        sizeOfModule += range.size;
    });


    var pattern = '70 B5 82 B0 06 46 01 20';  // 替换为实际的字节码模式

    Memory.scan(baseAddress, sizeOfModule, pattern, {
        onMatch: function (address, size) {
            var adjustedAddress = address.add(1);  // 调整地址
            console.log('Found SSL_write at: ' + adjustedAddress);
            Interceptor.attach(adjustedAddress, {
                onEnter: function (args) {
                    this.arg0 = args[0];
                    this.arg1 = args[1];
                    this.arg2 = args[2];
                    LogPrint("SSL_write called");
                    printNativeStack(this.context, "SSL_write");
                    var size = ptr(this.arg2).toInt32();
                    if (size &gt; 0) {
                        var sockfd = SSL_get_rfd(this.arg0);
                        var socketdetail = getsocketdetail(sockfd);
                        console.log(socketdetail + " - SSL_write - " + hexdump(this.arg1, { length: size }));
                    }
                },
                onLeave: function (retval) {
                    LogPrint("SSL_write return");
                }
            });
        },
        onComplete: function () {
            console.log('Scan complete');
        }
    });
}

function main() {
    hookMatchbytecode();
}

setImmediate(main);
</code></pre>
<ul>
<li><p>函数的字节码可以在IDA中提取</p>
<img lazyload="" src="/images/loading.svg" data-src="/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240706181440550.png" class="" title="image-20240706181440550">

<ul>
<li>需要注意32位和64位的字节码不同</li>
<li>不同版本的源码，字节码可能也不同</li>
<li>在不同环境下，字节码都可能有所不同，所以也不能通用。可以用作一种初筛的方式</li>
</ul>
</li>
<li><p>在我们的示例代码中以<code>libssl.so</code>库为例，实际使用中自己编译的openssl库可能会是其它的名字，需要先确定是哪一个库再进行扫描，扫描全部的库当然也行，不过比较消耗资源。</p>
</li>
</ul>
<p>测试结果：以腾讯新闻的App为例</p>
<img lazyload="" src="/images/loading.svg" data-src="/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240706220712317.png" class="" title="image-20240706220712317">

<p><strong>总结：不论是使用偏移还是字节码匹配的模式随着环境的改变都不稳定，最好的方式还是打印出来无符号堆栈和标准的有符号的堆栈进行对比，然后尝试找到正确的hook函数，另外也可以尝试去目标so库中搜索关键字符串尝试定位</strong></p>
<h2 id="直接使用系统调用"><a href="#直接使用系统调用" class="headerlink" title="直接使用系统调用"></a>直接使用系统调用</h2><p>在某些应用中，确实可能会使用汇编直接调用系统调用号来与内核交互，这样绕过了标准的库函数（如 <code>sendto</code>、<code>recvfrom</code>、<code>read</code> 和 <code>write</code>），使得传统的拦截方法失效。然而，Frida 仍然可以通过低层次的拦截来捕获这些操作，例如，通过拦截系统调用接口或使用更底层的内存拦截技术</p>
<h3 id="拦截系统调用"><a href="#拦截系统调用" class="headerlink" title="拦截系统调用"></a>拦截系统调用</h3><p>一种方法是拦截系统调用的入口点。在 Linux 系统中，系统调用通过 <code>syscall</code> 指令执行，可以通过拦截这些指令来捕获所有的系统调用。</p>
<img lazyload="" src="/images/loading.svg" data-src="/2024/07/29/%E8%87%AA%E7%BC%96%E8%AF%91openssl%E5%BA%93%E7%9A%84%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/image-20240706232355556.png" class="" title="image-20240706232355556">

<p>以<code>write</code>为例</p>
<pre><code class="js">const SYS_write = 4; // 在 ARM 上，write 的系统调用号是 4

// 帮助函数：打印被拦截的系统调用信息
function logSyscall(context, syscallNumber, args) {
    console.log("Syscall intercepted:");
    console.log("Syscall number:", syscallNumber);
    console.log("Arguments:", args);
    console.log("Context:", context);
}

// 帮助函数：处理被拦截的系统调用
function handleSyscall(context, syscallNumber, args) {
    if (syscallNumber === SYS_write) {
        logSyscall(context, syscallNumber, args);

        // 获取并打印写入的数据
        var fd = args[0].toInt32();
        var buffer = args[1];
        var count = args[2].toInt32();
        var data = buffer.readUtf8String(count);

        console.log("File descriptor:", fd);
        console.log("Data being written:", data);
    }
}

Interceptor.attach(Module.findExportByName(null, 'syscall'), {
    onEnter: function (args) {
        var syscallNumber = args[0].toInt32();
        handleSyscall(this.context, syscallNumber, args.slice(1));
    }
});
</code></pre>
<ul>
<li>代码有问题，暂未修改</li>
</ul>

		</div>

		
		<div class="post-copyright-info w-full my-8 px-2 sm:px-6 md:px-8">
			<div class="article-copyright-info-container">
    <ul>
        <li><strong>标题:</strong> 自编译openssl库的抓包与溯源</li>
        <li><strong>作者:</strong> xiaoeryu</li>
        <li><strong>创建于
                :</strong> 2024-07-29 08:19:13</li>
        
            <li>
                <strong>更新于
                    :</strong> 2024-07-29 09:45:38
            </li>
        
        <li>
            <strong>链接:</strong> https://github.com/xiaoeryu/2024/07/29/自编译openssl库的抓包与溯源/
        </li>
        <li>
            <strong>
                版权声明:
            </strong>
            

            
                本文章采用 <a class="license" target="_blank" rel="noopener" href="https://creativecommons.org/licenses/by-nc-sa/4.0">CC BY-NC-SA 4.0</a> 进行许可。
            
        </li>
    </ul>
</div>

		</div>
		

		
		<ul class="post-tags-box text-lg mt-1.5 flex-wrap justify-center flex md:hidden">
			
			<li class="tag-item mx-0.5">
				<a href="/tags/App%E6%8A%93%E5%8C%85/">#App抓包</a>&nbsp;
			</li>
			
			<li class="tag-item mx-0.5">
				<a href="/tags/Frida-Hook/">#Frida Hook</a>&nbsp;
			</li>
			
		</ul>
		

		

		
		<div class="article-nav my-8 flex justify-between items-center px-2 sm:px-6 md:px-8">
			
			<div class="article-prev border-border-color shadow-redefine-flat shadow-shadow-color-2 rounded-medium px-4 py-2 hover:shadow-redefine-flat-hover hover:shadow-shadow-color-2">
				<a class="prev" rel="prev" href="/2024/07/29/%E5%8D%8F%E8%AE%AE%E6%9E%9A%E4%B8%BE%E3%80%81%E7%88%86%E7%A0%B4%E5%8F%8A%E7%AE%97%E6%B3%95%E6%A8%A1%E6%8B%9F/">
					<span class="left arrow-icon flex justify-center items-center">
						<i class="fa-solid fa-chevron-left"></i>
					</span>
					<span class="title flex justify-center items-center">
						<span class="post-nav-title-item">协议枚举、爆破及算法模拟</span>
						<span class="post-nav-item">上一篇</span>
					</span>
				</a>
			</div>
			
			
			<div class="article-next border-border-color shadow-redefine-flat shadow-shadow-color-2 rounded-medium px-4 py-2 hover:shadow-redefine-flat-hover hover:shadow-shadow-color-2">
				<a class="next" rel="next" href="/2024/07/04/JNI%E5%B1%82SSL%E9%80%9A%E4%BF%A1%E6%8A%93%E5%8C%85%E4%B8%8E%E6%BA%AF%E6%BA%90/">
					<span class="title flex justify-center items-center">
						<span class="post-nav-title-item">JNI层SSL通信抓包与溯源</span>
						<span class="post-nav-item">下一篇</span>
					</span>
					<span class="right arrow-icon flex justify-center items-center">
						<i class="fa-solid fa-chevron-right"></i>
					</span>
				</a>
			</div>
			
		</div>
		


		
		<div class="comment-container px-2 sm:px-6 md:px-8 pb-8">
			<div class="comments-container mt-10 w-full ">
    <div id="comment-anchor" class="w-full h-2.5"></div>
    <div class="comment-area-title w-full my-1.5 md:my-2.5 text-xl md:text-3xl font-bold">
        评论
    </div>
    

        
            


        
    
</div>

		</div>
		
	</div>

	
	<div class="toc-content-container">
		<div class="post-toc-wrap">
	<div class="post-toc">
		<div class="toc-title">目录</div>
		<div class="page-title">自编译openssl库的抓包与溯源</div>
		<ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%BD%BF%E7%94%A8%E7%A7%81%E6%9C%89SSL%E5%BA%93%E9%80%9A%E4%BF%A1"><span class="nav-text">使用私有SSL库通信</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E7%BC%96%E5%86%99%E8%84%9A%E6%9C%AC"><span class="nav-text">编写脚本</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E6%89%A9%E5%A4%A7%E6%90%9C%E7%B4%A2%E8%8C%83%E5%9B%B4"><span class="nav-text">扩大搜索范围</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%97%A0%E7%AC%A6%E5%8F%B7%E5%BA%93"><span class="nav-text">无符号库</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%B8%B8%E7%94%A8%E7%9A%84%E4%B8%89%E7%A7%8D%E6%96%B9%E6%B3%95"><span class="nav-text">常用的三种方法</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#%E5%AD%97%E8%8A%82%E5%8C%B9%E9%85%8D%E6%A8%A1%E5%BC%8F%E8%84%9A%E6%9C%AC%EF%BC%9A"><span class="nav-text">字节匹配模式脚本：</span></a></li></ol></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%9B%B4%E6%8E%A5%E4%BD%BF%E7%94%A8%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8"><span class="nav-text">直接使用系统调用</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%8B%A6%E6%88%AA%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8"><span class="nav-text">拦截系统调用</span></a></li></ol></li></ol>

	</div>
</div>
	</div>
	
</div>
			</div>

			
		</div>

		<div class="main-content-footer">
			<footer class="footer mt-5 py-5 h-auto text-base text-third-text-color relative border-t-2 border-t-border-color">
    <div class="info-container py-3 text-center">
        
        <div class="text-center">
            &copy;
            
              <span>2022</span>
              -
            
            2025&nbsp;&nbsp;<i class="fa-solid fa-heart fa-beat" style="--fa-animation-duration: 0.5s; color: #f54545"></i>&nbsp;&nbsp;<a href="/">xiaoeryu</a>
            
                
                <p class="post-count space-x-0.5">
                    <span>
                        共撰写了 112 篇文章
                    </span>
                    
                </p>
            
        </div>
        
            <script data-swup-reload-script src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
            <div class="relative text-center lg:absolute lg:right-[20px] lg:top-1/2 lg:-translate-y-1/2 lg:text-right">
                
                    <span id="busuanzi_container_site_uv" class="lg:!block">
                        <span class="text-sm">访问人数</span>
                        <span id="busuanzi_value_site_uv"></span>
                    </span>
                
                
                    <span id="busuanzi_container_site_pv" class="lg:!block">
                        <span class="text-sm">总访问量</span>
                        <span id="busuanzi_value_site_pv"></span>
                    </span>
                
            </div>
        
        <div class="relative text-center lg:absolute lg:left-[20px] lg:top-1/2 lg:-translate-y-1/2 lg:text-left">
            <span class="lg:block text-sm">由 <?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="relative top-[2px] inline-block align-baseline" version="1.1" id="圖層_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1rem" height="1rem" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve"><path fill="#0E83CD" d="M256.4,25.8l-200,115.5L56,371.5l199.6,114.7l200-115.5l0.4-230.2L256.4,25.8z M349,354.6l-18.4,10.7l-18.6-11V275H200v79.6l-18.4,10.7l-18.6-11v-197l18.5-10.6l18.5,10.8V237h112v-79.6l18.5-10.6l18.5,10.8V354.6z"/></svg><a target="_blank" class="text-base" href="https://hexo.io">Hexo</a> 驱动</span>
            <span class="text-sm lg:block">主题&nbsp;<a class="text-base" target="_blank" href="https://github.com/EvanNotFound/hexo-theme-redefine">Redefine v2.8.2</a></span>
        </div>
        
        
            <div>
                博客已运行 <span class="odometer" id="runtime_days" ></span> 天 <span class="odometer" id="runtime_hours"></span> 小时 <span class="odometer" id="runtime_minutes"></span> 分钟 <span class="odometer" id="runtime_seconds"></span> 秒
            </div>
        
        
            <script data-swup-reload-script>
                try {
                    function odometer_init() {
                    const elements = document.querySelectorAll('.odometer');
                    elements.forEach(el => {
                        new Odometer({
                            el,
                            format: '( ddd).dd',
                            duration: 200
                        });
                    });
                    }
                    odometer_init();
                } catch (error) {}
            </script>
        
        
        
    </div>  
</footer>
		</div>
	</div>

	
	<div class="post-tools">
		<div class="post-tools-container">
	<ul class="article-tools-list">
		<!-- TOC aside toggle -->
		
		<li class="right-bottom-tools page-aside-toggle">
			<i class="fa-regular fa-outdent"></i>
		</li>
		

		<!-- go comment -->
		
		<li class="go-comment">
			<i class="fa-regular fa-comments"></i>
		</li>
		
	</ul>
</div>
	</div>
	

	<div class="right-side-tools-container">
		<div class="side-tools-container">
	<ul class="hidden-tools-list">
		<li class="right-bottom-tools tool-font-adjust-plus flex justify-center items-center">
			<i class="fa-regular fa-magnifying-glass-plus"></i>
		</li>

		<li class="right-bottom-tools tool-font-adjust-minus flex justify-center items-center">
			<i class="fa-regular fa-magnifying-glass-minus"></i>
		</li>

		<li class="right-bottom-tools tool-dark-light-toggle flex justify-center items-center">
			<i class="fa-regular fa-moon"></i>
		</li>

		<!-- rss -->
		

		

		<li class="right-bottom-tools tool-scroll-to-bottom flex justify-center items-center">
			<i class="fa-regular fa-arrow-down"></i>
		</li>
	</ul>

	<ul class="visible-tools-list">
		<li class="right-bottom-tools toggle-tools-list flex justify-center items-center">
			<i class="fa-regular fa-cog fa-spin"></i>
		</li>
		
		<li class="right-bottom-tools tool-scroll-to-top flex justify-center items-center">
			<i class="arrow-up fas fa-arrow-up"></i>
			<span class="percent"></span>
		</li>
		
		
	</ul>
</div>
	</div>

	<div class="image-viewer-container">
	<img src="">
</div>

	
	<div class="search-pop-overlay">
	<div class="popup search-popup">
		<div class="search-header">
			<span class="search-input-field-pre">
				<i class="fa-solid fa-keyboard"></i>
			</span>
			<div class="search-input-container">
				<input autocomplete="off" autocorrect="off" autocapitalize="off" placeholder="站内搜索您需要的内容..." spellcheck="false" type="search" class="search-input">
			</div>
			<span class="popup-btn-close">
				<i class="fa-solid fa-times"></i>
			</span>
		</div>
		<div id="search-result">
			<div id="no-result">
				<i class="fa-solid fa-spinner fa-spin-pulse fa-5x fa-fw"></i>
			</div>
		</div>
	</div>
</div>
	

</main>



<script src="/js/build/libs/Swup.min.js"></script>

<script src="/js/build/libs/SwupSlideTheme.min.js"></script>

<script src="/js/build/libs/SwupScriptsPlugin.min.js"></script>

<script src="/js/build/libs/SwupProgressPlugin.min.js"></script>

<script src="/js/build/libs/SwupScrollPlugin.min.js"></script>

<script src="/js/build/libs/SwupPreloadPlugin.min.js"></script>

<script>
    const swup = new Swup({
        plugins: [
            new SwupScriptsPlugin({
                optin: true,
            }),
            new SwupProgressPlugin(),
            new SwupScrollPlugin({
                offset: 80,
            }),
            new SwupSlideTheme({
                mainElement: ".main-content-body",
            }),
            new SwupPreloadPlugin(),
        ],
        containers: ["#swup"],
    });
</script>




	
<script src="/js/build/tools/imageViewer.js" type="module"></script>

<script src="/js/build/utils.js" type="module"></script>

<script src="/js/build/main.js" type="module"></script>

<script src="/js/build/layouts/navbarShrink.js" type="module"></script>

<script src="/js/build/tools/scrollTopBottom.js" type="module"></script>

<script src="/js/build/tools/lightDarkSwitch.js" type="module"></script>

<script src="/js/build/layouts/categoryList.js" type="module"></script>



    
<script src="/js/build/tools/localSearch.js" type="module"></script>




    
<script src="/js/build/tools/codeBlock.js" type="module"></script>




    
<script src="/js/build/layouts/lazyload.js" type="module"></script>




    
<script src="/js/build/tools/runtime.js"></script>

    
<script src="/js/build/libs/odometer.min.js"></script>

    
<link rel="stylesheet" href="/assets/odometer-theme-minimal.css">




  
<script src="/js/build/libs/Typed.min.js"></script>

  
<script src="/js/build/plugins/typed.js" type="module"></script>








    
<script src="/js/build/libs/anime.min.js"></script>





    
<script src="/js/build/tools/tocToggle.js" type="module" data-swup-reload-script=""></script>

<script src="/js/build/layouts/toc.js" type="module" data-swup-reload-script=""></script>

<script src="/js/build/plugins/tabs.js" type="module" data-swup-reload-script=""></script>




<script src="/js/build/libs/moment-with-locales.min.js" data-swup-reload-script=""></script>


<script src="/js/build/layouts/essays.js" type="module" data-swup-reload-script=""></script>





	
</body>

</html>